/* 
Copyright (c) 2004,2005, 2006, 2007, 2008 University of Illinois 

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:

1. Redistributions of source code must retain the above copyright
   notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
   notice, this list of conditions and the following disclaimer in the
   documentation and/or other materials provided with the distribution.

3. The name of the author may not be used to endorse or promote products
   derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
//if(!AccessibilityExtension)
//    throw "Failed to load AccessibilityExtension";
//else
//    throw "AccessibilityExtension loaded in UNIVERSAL";

AccessibilityExtension.ns(function() { with (AccessibilityExtension.LIB) {
	var AE = AccessibilityExtension.ns;  
	var accessibilityextURI = "accessibilityext:aeURI";
	var lowercaseCharacters='abcdefghijklmnopqrstuvwxyz';
	var uppercaseCharacters='ABCDEFGHIJKLMNOPQRSTUVWXYZ';
		
/*	var cached_properties = AccessibilityExtension.LIB.loadProperties();
  	var xpProvider = new this.accessext_provider();
		accessext_provider.prototype = {
		   activeDoc : null,
		   getElementsByXPath : function(xp,callback) {
		      if (this.activeDoc == null) {
		         return false;
		      }
		      var a = new Array();
		      var xpathResult = this.activeDoc.evaluate(xp, node, accessext_NSResolver, XPathResult.ANY_TYPE,null);
		      var r = xpathResult.iterateNext();
		      while (r) {
		         if (callback != false) {
		            callback(r);
		         } else {
		            a[a.length] = r;
		         }
		      }
		      r = xpathResult.iterateNext();
		      return a;
		   },
		   getHeaders : function(callback) {
		      var xp = "//h1|//h2|//h3|//h4|//h5|//h6";
		      var headers = this.getElementsByXPath(xp,callback);
		      return headers;
		   },
		   getDataTables : function() {
		      
		   },
		   getImages : function() {
		   
		   },
		   getMaps : function(callback) {
		      var xp = "//map";
		   },
		   getNavigationLists: function(callback) {
		      var xp = "//map|//ul|//ol|//dl|//nl";
		      var navlists = this.getElementsByXPath(xp,callback);
		      return navlists;
		   },
		   getElementsWithStyle : function(callback) {
		      var xp = "//*[@class]|//*[@style]";
		      return this.getElementsByXPath(xp,callback);
		   }
		};
*/
AccessibilityExtension.LIB.extend(AccessibilityExtension.LIB,
{
	accessext_provider: function(doc) {
	   this.activeDoc = doc;
	},
	// Returns a reference to the preferences service, with the branch string "accessext." automatically prefixed to all queries
	getAccessextPrefService: function() {
	   return Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("accessext.");
	},
	
	assert: function(fact) {
	     if (!fact) {
	          alert("Assert failure!");
	     }
	},
	
	/* Global classes */
	
	accessext_docUsesNamespace: function(doc) {
	   return doc.documentElement.hasAttributeNS("http://www.w3.org/2000/xmlns/","xmlns");
	},
	
	accessext_nodeUsesNamespace: function(node) {
	   if (null != node) {
	   return accessext_docUsesNamespace(node.ownerDocument);
	   }
	   return false;
	},
	
	accessext_NSResolver: function(prefix) {
	    switch (prefix) {
	      case 'html':
	         return 'http://www.w3.org/1999/xhtml';
	      case 'x2':
	         return 'http://www.w3.org/TR/xhtml2';
	      case 'x':
	         return 'http://www.w3.org/1999/xhtml';
	      case 'xhtml':
	         return 'http://www.w3.org/1999/xhtml';
	      default:
	         accessext_consoleDump("Unknown prefix: " + prefix);
	         return null;
	   }
	   return null;
	},
	
	accessext_getFirstHeaderNode: function(node) {
	   var xp;
	   if (accessext_nodeUsesNamespace(node)) {
	      xp = ".//html:h1|.//html:h2|.//html:h3|.//html:h4|.//html:h5|.//html:h6";
	   } else {
	      xp = ".//h1|.//h2|.//h3|.//h4|.//h5|.//h6";
	   }
	   var xpathResult = node.ownerDocument.evaluate(xp, node, accessext_NSResolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);
	   var r = xpathResult.iterateNext();
	   if (r) {
	      return r;
	   }
	   return false;
	},
	isNavList: function(node) {
		try {
			var xp = "count(li)";
			var total = 0;
			
			var totalLI = node.ownerDocument.evaluate(xp,node,accessext_NSResolver,XPathResult.ANY_TYPE,null).numberValue;
			if (totalLI == 0) return false;
			for (var i=0; i < node.childNodes.length; i++) {
		 		var temp = node.childNodes[i].ownerDocument.evaluate('count(a)',node.childNodes[i],accessext_NSResolver,XPathResult.ANY_TYPE,null).numberValue;
		 		if (temp > 0) {
					total++;
				} else {
					var xpathResult =node.childNodes[i].ownerDocument.evaluate('descendant::ul',node,accessext_NSResolver,XPathResult.ANY_TYPE,null);
					var r = xpathResult.iterateNext();
					while (r) {
						if (isNavList(r) == true) total++;
						r = xpathResult.iterateNext();
					}
				}		
			}
		} catch (ex) {
			accessext_consoleDump('isNavList exception: ', ex);
		}
	    if (total >= totalLI-1) return true;
		return false;
	},
	
	isNavTable: function(node) {
	   var xp;
	/*   if (accessext_nodeUsesNamespace(node)) { //check only the td of the current table
	     xp = "count(ancestor::html:table)";
	   } else {
	      xp = "count(ancestor::table)";
	   }
	   var total = node.ownerDocument.evaluate(xp,node,accessext_NSResolver,XPathResult.ANY_TYPE,null).numberValue;
	   if (total < 1) return false; //make sure it is a nested table
	*/
	   if (node.rows.length > 1) {
			for (r = 0; r < node.rows.length; r ++) {if (node.rows[r].cells.length > 1) return false; } 
	   }
	
	   var total;   
	   for (r = 0, total = 0; r < node.rows.length; r ++) {total += node.rows[r].cells.length;} //check only the td of the current table
	   if (total <= 1) return false; //make sure it has more than one cell, try to elminiate contain tables 
	
	   if (accessext_nodeUsesNamespace(node)) { //looks for links in the td of the table or child tables ignore link in li since they will be picked as ul/ol nav lists
	      xp = "count(child::*//html:a[not(ancestor::html:li)])";
	   } else {
	      xp = "count(child::*//a[not(ancestor::li)])";
	   }
	
	   var totalWithLinks = node.ownerDocument.evaluate(xp,node,accessext_NSResolver,XPathResult.ANY_TYPE,null).numberValue;
	   return ((totalWithLinks / total) >= 0.80);
	},
	
	isNavMap: function(node) {
	   var xp;
	   if (accessext_nodeUsesNamespace(node)) {
	      xp = "count(html:area)";
	   } else {
	      xp = "count(area)";
	   }
	   var total = node.ownerDocument.evaluate(xp,node,accessext_NSResolver,XPathResult.ANY_TYPE,null).numberValue;
	    
	   if (accessext_nodeUsesNamespace(node)) {
	       xp = "count(html:area[@href])";
	   } else {
	 	  xp = "count(area[@href])"
	   }
	
	   var totalWithLinks = node.ownerDocument.evaluate(xp,node,accessext_NSResolver,XPathResult.ANY_TYPE,null).numberValue;
	   return ((totalWithLinks / total) >= 0.80);
	},
	
	/*
	Method to determine if a table is a data table:
	--Be advised: this is a very preliminary method--
	The basic method is to iterate over every child node of a table until
	we encounter a <td> with a headers attribute
	The table might not really be a data table, but these are usually good indicators.
	
	Nested tables are ignored.
	*/
	accessext_isDataTable: function(tablenode) {
		try {
		   var r;
		   var c;
		   if(tablenode.rows.length<2) return false;
		   if(tablenode.rows[0].cells.length>=2 || tablenode.rows[tablenode.rows.length-1].cells.length>=2) {
		      if (tablenode.tHead) return true; //has tHead row(s)
			  if (tablenode.caption && cleanSpaces(tablenode.caption.textContent) != 0) return true;
			  if (tablenode.summary && cleanSpaces(tablenode.summary) != 0) return true;
		   } 
		   for (r=0; r < tablenode.rows.length; r++) { //contains data from tablenode.tHead.rows &  tablenode.tFoot.rows
		      if(tablenode.rows[r].cells.length>=2 || tablenode.rows[tablenode.rows.length-1].cells.length>=2) {
				  for (c=0; c < tablenode.rows[r].cells.length; c++) {
			         if (tablenode.rows[r].cells[c].tagName.toLowerCase() == 'th') {
			            return true;
			         }else { // assume these are 'td' cells
					 	var headers = GetValueFromAttributes(tablenode.rows[r].cells[c],['headers'],"");
					 	if (headers.length > 0) return true;
					 	if (GetValueFromAttributes(tablenode.rows[r].cells[c],['scope'],"") != "") return true;
					 }
			      }
			  }
		   }
		   return false;
		} catch (ex) {
			accessext_consoleDump('accessext_isDataTable exception: ', ex);
		}
	},
	
	//This assumes that accessext_isDataTable: function() is true.
	accessext_isComplexDataTable: function(node) {
		try {
			function isInteger(sText)	{
				var ValidChars = "0123456789";
				if (sText.length == 0) return false;
				for (i = 0; i < sText.length; i++) { 
					if (ValidChars.indexOf(sText.charAt(i)) == -1) return false;
				}
				return true;
			}		
			var r;
			var c;
			var allTH = 0;
		    if (node.tHead && node.tHead.rows.length > 1) return true; //more than one row in a thead element 
			for (r=0; r < node.rows.length; r++) { 
				var foundTD = false;
				var thCount = 0;				
				for (c=0; c < node.rows[r].cells.length; c++) {
					if (node.rows[r].cells[c].tagName.toLowerCase() == 'th') thCount++;
					var span = GetValueFromAttributes(node.rows[r].cells[c],['rowspan'],"");
					if (span != "" && isInteger(span) && span > 1) {return true;}
					span = GetValueFromAttributes(node.rows[r].cells[c],['colspan'],"");
					if (span != "" && isInteger(span) && span > 1) {return true;}				
					if (node.rows[r].cells[c].tagName.toLowerCase() == 'td') {
						foundTD = true;
						var headersStr = GetValueFromAttributes(node.rows[r].cells[c],['headers'],"");
						if (headersStr.length > 1) {
							var headers = headersStr.split(' ');
							if (headers.length > 2) {return true;}
						}
					}
				}
				if (thCount == node.rows[r].cells.length) {allTH++;}
				else if (foundTD && thCount > 1){return true;} //there is more than one th element in a row that also includes td elements
				if (allTH >= 2) {return true;} // there are two or more rows that contain only th elements in the table. 
			}
			return false;
		} catch (ex) {
			accessext_consoleDump('isComplexDataTable exception: ', ex);
		}
	},
	
	/*
	Firefox and Mozilla, prior to Gecko 1.8, would get certain attributes from various
	nodes as display text when using node.textContent. This seems to go against the DOM3
	spec, and was changed in 1.8. Because of this, certain lists no longer get the
	text they should.
	
	In particular, <a><img alt="something"/></a>, used to have "something" as the a's textcontent
	*/
	
	GetTextOnlyFromNode: function(node) {
	   assert(node != null);
	   if(node.textContent) {
	      var text = cleanSpaces(node.textContent);
	
	      if (text) {
	         return text;
	      }
	      text = GetValueFromAttributes(node,['value'],false);
	      if (text) {
	         return text;
	      }
	      return accessext_getBundleSingleString('none');
	   } else {
	      var retstring = GetTextOnlyFromNodeInternal(node);
	      if(retstring == null || retstring == '' || retstring == ' ') {
	         return accessext_getBundleSingleString('noAvailText');
	      }
	      return retstring;
	   }
	},
	
	GetTextOnlyFromNodeInternal: function(node) {
	   var retstring;
	   if(node.nodeValue
	         && node.nodeValue != null) {
	      retstring = cleanSpaces(node.nodeValue);
	      if(retstring != '') {
	         return retstring;
	      }
	   }
	   if(node.hasChildNodes && !node.hasChildNodes()) {
	      return null;
	   }
	   retstring = '';
	   for (var i = 0; i < node.childNodes.length; i++) {
	      if(node.childNodes[i].nodeName && node.childNodes[i].nodeName.toLowerCase() == "br") {
	         retstring += ' ';
	      } else {
	         var temp = GetTextOnlyFromNodeInternal(node.childNodes[i]);
	         if(temp != null && temp != '') {
	            retstring += temp;
	         }
	      }
	   }
	   return retstring;
	},
	
	// Looks like it returns the given string str without leading/trailing whitespace 
	// ... and with consecutive spaces replaced with only one space
	cleanSpaces: function(str) {
	   return trim(str.replace(/\s+/g,' '));
	},
	
	trim: function(str) {
	   return str.replace(/^\s*|\s*$/g,"");
	},
	
	GetValueFromAttributes: function(node,attributeArray,defaultValue) {
	   for (var i=0; i < attributeArray.length; i++) {
	      if (node.hasAttribute &&
	         node.hasAttribute(attributeArray[i])) {
	         var value = node.getAttribute(attributeArray[i]);
	         var temp = cleanSpaces(value);
	         if (temp.length > 0) {
	            return temp;
	         }
	      }
	   }
	   return defaultValue;
	},
	
	GetLinkFromNode: function(node) {
	   return GetValueFromAttributes(node,['href','src'],accessext_getBundleSingleString('noAddress'));
	},
	
	GetTitleFromNode: function(node) {
	   return GetValueFromAttributes(node,['title'],accessext_getBundleSingleString('noTitle'));
	},
	
	GetAltFromNode: function(node) {
	   return GetValueFromAttributes(node,['alt'],accessext_getBundleSingleString('noAlt'));
	},
	
	GetUserAltFromNode: function(node) {
	   return GetValueFromAttributes(node,['alt','title'],accessext_getBundleSingleString('noAltOrTitle'));
	},
	
	accessext_disableAttribute: function(node,attribute,prefix,enable) {
	   if (node && node.hasAttribute) {
	      var newName = prefix+"-"+attribute;
	      if (enable) {
	         if (node.hasAttribute(attribute)) {
	            node.setAttribute(newName,node.getAttribute(attribute));
	            node.removeAttribute(attribute);
	         }
	      } else if (node.hasAttribute(newName)) {
	         node.setAttribute(attribute,node.getAttribuite(newName));
	         node.removeAttribute(newName);
	      }
	   }
	},
	
	/**
	 * Creates the global accessext-highlight-div highlight node.
	 * @return highlightNode the global accessext-highlight-div highlight node.
	 * @author Sandy Foltz
	*/
	accessext_CreateHighlightNode: function() {
		highlightNode = AE.mydoc.defaultView.document.createElement('div');
		highlightNode.setAttribute('id', 'accessext-highlight-div');
		highlightNode.setAttribute('class', 'accessext-highlight-div');
				
	   	var prefService = getAccessextPrefService();
		highlightColor = prefService.getCharPref("accessext.options.highlight.color");
		highlightBorder = prefService.getCharPref("accessext.options.highlight.border");
		
		return highlightNode;		
	},
	
	PositionOffScreen: function(node) {
		if (node != null) {
			var r = accessext_getNodeDocPosition(node);
			if (((r.x < 0) || (r.y < 0)) || (node.nodeName.toUpperCase() == 'HIDDEN') || ((r.width <= 0) && (r.height <= 0)) ) {
				return true;
			}
		}
		return false;
	},
	
	/**
	 * Moves what you see highlighted by changing the position in the style of the accessext-highlight-div node.
	 * @param {node} node Node in the target browzser window to overlay with highlighting
	 * @return nothing
	 * @author Sandy Foltz
	 * @see accessext_treeboxScrollAndHighlightNode accessext_treeboxScrollAndHighlightNode() in list_universal.js
	*/
	accessext_PositionHighlightNode: function(node) {
		var style;
		var r = new Object();
		
		if (node != null) {
			r = accessext_getNodeDocPosition(node);
			
			if (((r.x < 0) || (r.y < 0)) || (node.nodeName.toUpperCase() == 'HIDDEN') || ((r.width <= 0) && (r.height <= 0)) ) {
				if ((r.x < 0) || (r.y < 0)) {
					r.x = 10; r.y = 10;
					accessext_scrollToNodeCentered(highlightNode);
				}
				if ((r.width <= 2) && (r.height <= 2)) { r.width = r.height = 200; }
				if (node.nodeName.toUpperCase() == 'HIDDEN') {
					highlightNode.appendChild(AE.mydoc.defaultView.document.createTextNode("Tag type <Hidden>"));
				} else {
					//SMF 9/20/07 used in navlist highlighting when the navigation is off screen but the header is visible, highlight the header
					if (node.relatednode != null) { 
						//SMF add check for if this node is visible
						r = accessext_getNodeDocPosition(node.relatednode);
					} else {
						highlightNode.appendChild(AE.mydoc.defaultView.document.createTextNode("Position Off Screen"));
					}			
				}			
			}
		} else {
		  r.x = r.y = r.width = r.height = 0;
		}
	
	 	if (node.nodeName.toUpperCase() != 'AREA'){
			style = 'position:absolute;top:' + r.y + 'px;left:' + r.x + 'px;width:' + r.width + 'px;height:' + r.height + 'px'
		} else {
			var coords = node.getAttribute('coords');
			if (coords == null) {
				if ((r.width <= 2) && (r.height <= 2)) { r.width = r.height = 200; }
				style = 'position:absolute;top:' + r.y + 'px;left:' + r.x + 'px;width:' + r.width + 'px;height:' + r.height + 'px'
				highlightNode.appendChild(AE.mydoc.defaultView.document.createTextNode("Position Off Screen"));
			} else {		
				var coordArray=coords.split(",");
				if (node.getAttribute('shape').toUpperCase() == 'RECT') {
					r.x = r.x + Number(coordArray[0]);
					r.y = r.y + Number(coordArray[1]);
					r.width = Number(coordArray[2]) - Number(coordArray[0]); 
					r.height = Number(coordArray[3]) - Number(coordArray[1]);  
				} else if (node.getAttribute('shape').toUpperCase() == 'CIRCLE') {
					r.x = r.x + Number(coordArray[0]) - Number(coordArray[2]);
					r.y = r.y + Number(coordArray[1]) - Number(coordArray[2]);
					r.width = r.height = Number(coordArray[2]) * 2; 		
				} else if (node.getAttribute('shape').toUpperCase() == 'POLY') {
					var xMin = coordArray[0];
					var xMax = coordArray[0];
					var yMin = coordArray[1];
					var yMax = coordArray[1];
					for (var counter = 2; counter < coordArray.length; counter += 2) {
						xMin = Math.min(coordArray[counter],xMin);
						xMax = Math.max(coordArray[counter],xMax);
						yMin = Math.min(coordArray[counter+1],yMin);
						yMax = Math.max(coordArray[counter+1],yMax);
					}
					r.x = r.x + xMin;
					r.y = r.y + yMin;
					r.width = xMax - xMin;
					r.height = yMax - yMin;		
				}
				style = 'position:absolute;top:' + r.y + 'px;left:' + r.x + 'px;width:' + r.width + 'px;height:' + r.height + 'px';
			}
		}	
		var strColor = highlightColor; //EnsureHighlightColorVisibility(node); the background color returned via getComputedStyle can be wildly different than the color you see (jpg on top ect.)
	//	style = style + ';background-color:' + strColor + ';border: ' + highlightBorder + ';border-style: dashed;opacity:0.5;visibility:show;font-style:bold';	
		style = style + ';background-color:' + strColor + ';border: ' + highlightBorder + ';border-style: dashed;opacity:0.5';	
			
		highlightNode.setAttribute('style', style);	
	},
	
	/**
	 * Attaches the accessext-highlight-div highlight node to the DOM at or around node.
	 * @param {node} node Node in the target browzser window to overlay with highlighting
	 * @return nothing
	 * @author Sandy Foltz
	 * @see accessext_treeboxScrollAndHighlightNode accessext_treeboxScrollAndHighlightNode() in list_universal.js
	*/
	accessext_AttachHighlightNode: function(node) {
		
		var frameNode = AE.mydoc.defaultView.document.getElementsByTagName("frame");
		if (frameNode.length == 0) {
			frameNode = AE.mydoc.defaultView.document.getElementsByTagName("iframe");
			if (frameNode.length == 0) {
				var htmlNode = AE.mydoc.defaultView.document.getElementsByTagName("html");
				htmlNode[0].appendChild(highlightNode);		
				return;
			}
		}
		
		//attach the node at the correct location (within the frame - can't see the highlight if on the frame itself)
		if ((node.nodeName.toUpperCase() == 'INPUT') || (node.nodeName.toUpperCase() == 'TEXTAREA') || (node.nodeName.toUpperCase() == 'SELECT') || (node.nodeName.toUpperCase() == 'IMG') || (node.nodeName.toUpperCase() == 'AREA') || (node.nodeName.toUpperCase() == 'A')) {
			if (node.parentNode.nodeName.toUpperCase() == 'MAP') {
				var tmp = node.parentNode;
				if (tmp.parentNode){
					tmp.parentNode.appendChild(highlightNode);
				}else {
					alert('Unable to attach highlight node in a page with frames.')
				}
			} else {
				node.parentNode.appendChild(highlightNode);
			}
		} else {
			node.appendChild(highlightNode);
		}			
	},
	
	accessext_styleNode: function(node,attributes,enabled) {
	//Method used to set each individual DOM node	
	   if (node.hasAttribute) {
	      if (enabled) {
	         if (!node.hasAttribute('accessext-styletext')) {
	            node.setAttribute('accessext-styletext',node.style.cssText);
	         }
	         for (var a in attributes) {
	            node.style[a]=attributes[a];
	         }
	      } else if (node.hasAttribute('accessext-styletext')) {
	         node.style.cssText = node.getAttribute('accessext-stlyetext');
	      }
	   } 
	 
	   return node;
	},
	
	accessext_scrollAndStyleNode: function(node,adoc,awindow,styleString) {
	   accessext_styleNode(node,styleString);
	   ScrollToNode(node,adoc,awindow);
	},
	
	ScrollToNode: function(node, adoc, awindow) {
	   if(node != null && adoc != null) {
	      var destx = node.offsetLeft;
	      var desty = node.offsetTop; 
	      var thisNode = node; 
	      while (thisNode.offsetParent && (thisNode.offsetParent != adoc.body)) { 
	         thisNode = thisNode.offsetParent;
	         destx += thisNode.offsetLeft; 
	         desty += thisNode.offsetTop; 
	      }
	      awindow.scrollTo(destx, desty);
	   }
	},
	
	HighlightNode: function(node, backgroundColor) {
	   if(node
	         && backgroundColor 
	         && node.style 
	         && node.style.backgroundColor) {
	      node.style.backgroundColor = backgroundColor;
	      return node;
	   }
	   return null;
	},
	
	accessext_disable_attributes_recursively: function(node, attributes, disable) {
	   var i = 0;
	   var tempNode;
	   //for (a in attributes) {  11/19/08 SMF this seems to be picking up functions as well as attributes
	   for (var a = 0; a < attributes.length; a++) {
	      var atr = attributes[a];
	      var str = "accessext-remove-attribute-" + atr;
	      var attrToRemove = disable ? atr : str;
	      var attrToCreate = disable ? str : atr;
	      var nodesWithAttr = getNodesWithAttribute(node,attrToRemove);
	
	      for (i=0; i < nodesWithAttr.length; i++) {
	         tempNode = nodesWithAttr[i];
	         if (tempNode.hasAttribute && tempNode.hasAttribute(attrToRemove)) {
	            tempNode.setAttribute(attrToCreate, tempNode.getAttribute(attrToRemove));
	//SMF 2/29/07 Window events only seem to get disabled if you leave the event and make the action null			
	//           tempNode.removeAttribute(attrToRemove);
	            tempNode.setAttribute(attrToRemove, null);
	          }
	      }
	   }
	},
	
	accessext_rename_node: function(node,newname) {
	   assert(node != null);
	   var copy = node.ownerDocument.createElement(newname);
	   var i;
	   for(i=0; i < node.attributes.length; i ++) {
	      copy.setAttribute(node.attributes[i].nodeName,node.attributes[i].nodeValue);
	   }
	   if(node.childNodes) {
	      for(i=0; i < node.childNodes.length; i ++) {
	         if (node.childNodes[i].cloneNode) {
	            copy.appendChild(node.childNodes[i].cloneNode(true));
	         } else {
	            copy.appendChild(node.childNodes[i]);
	         }
	      }
	   }
	   if (node.parentNode) {
	      node.parentNode.replaceChild(copy,node);
	   }
	   return copy;
	},
	
	/*tags is an object w/ associative mappings:
	nameToSet=>{list of tags to convert to nameToSet}
	*/
	accessext_rename_recursively: function(node, translateOn, tags) {
	   assert(node != null);
	   var r,t,i,s;
	   var replaced;
	
	   var xp = "";
	   var tempNode;
	   try {
	      if (translateOn) {
	         // for (tagToCreate in tags) {  11/19/08 SMF this seems to be picking up functions as well as attributes
	    	  for (var tagToCreate = 0; tagToCreate < tags.length; tagToCreate++) {  
	            xp = "";
	            for (i = 0; i < tags[tagToCreate].length; i++) {
	               if (i > 0) {
	                  xp = xp + "|";
	               }
	               xp = xp + "//" + tags[tagToCreate][i];
	            }
	            var nodes = getAllXpathResultNodes(node,xp);
	            for (i = 0; i < nodes.length; i++) {
	               tempNode = nodes[i];
	               tempNode.setAttribute("accessext-tag-renamed",tempNode.tagName);
	               tempNode.setAttribute("accessext-tag-renamed-lower",tempNode.tagName.toLowerCase());
	               tempNode = accessext_rename_node(tempNode,tags[tagToCreate]);
	               renamedNodes.push(tempNode);
	            }
	         }
	      } else {
	          // for (tagToCreate in tags) {  11/19/08 SMF this seems to be picking up functions as well as attributes
	          for (var tagToCreate = 0; tagToCreate < tags.length; tagToCreate++) {  
	            xp = "";
	            for (i = 0; i < tags[tagToCreate].length; i++) {
	               if (i > 0) {
	                  xp = xp + "|";
	               }
	               xp = xp + "//" + tags[tagToCreate] + "[@accessext-tag-renamed-lower=\'" + tags[tagToCreate][i].toLowerCase() + "\']";
	            }
	            var nodes = getAllXpathResultNodes(node,xp);
	            for (i = 0; i < nodes.length; i++) {
	               tempNode = nodes[i];
	               tempNode = accessext_rename_node(tempNode,tempNode.getAttribute("accessext-tag-renamed"));
	               tempNode.removeAttribute("accessext-tag-renamed");
	               tempNode.removeAttribute("accessext-tag-renamed-lower");
	               renamedNodes.push(tempNode);
	            }
	         }
	      }
	   } catch (ex) {
	      accessext_consoleDump('Unhandled exception in accessext_rename_recursively : ' + ex);
	   }  
	},
	
	//Unused
	accessext_change_attribute_recursively: function(node, attribute, val) {
	   if(node.getAttribute && node.getAttribute(attribute)) {
	      node.setAttribute(attribute, val);
	   }
	   if(node.childNodes) {
	      for(var i=0; i<node.childNodes.length; i++) {
	         accessext_remove_attribute_recursively(node.childNodes[i], attribute);
	      }
	   }
	},
	
	/*
	 * Much of the code below is heavily borrowed from the Web Developer plugin
	 *    by Chris Pederick and is available at
	 *    http://www.chrispederick.com/work/firefox/webdeveloper/
	 */
	accessext_buildDocumentList: function(doc) {
	   var documentList = new Array();
	   if (doc == null) {
	      documentList     = accessext_getDocuments(AE.thewindow, new Array());
	   } else {
	      documentList.push(doc);
	   }
	   return documentList;
	},
	 
	accessext_getDocuments: function(objWithFrames, documentList) {
	   documentList.push(objWithFrames.document);
	
	   if (null != objWithFrames.frames) {
	      var framesList = objWithFrames.frames;
	      
	      // Loop through the frames
	      for(var i = 0; i < framesList.length; i++) {
	         accessext_getDocuments(framesList[i], documentList);
	      }
	   }
	   return documentList;
	},
	
	getActiveBrowser: function() {
	   return window.top.getBrowser().browsers[window.top.getBrowser().mTabBox.selectedIndex];
	},
	
	accessext_getDocumentsFromActiveBrowser: function() {
	   var browser = getActiveBrowser();
	      
	   if (null == browser) {
	      accessext_consoleDump("accessext_getDocumentsFromActiveBrowser: linked browser not found");
	      return new Array();
	   }
	   
	   if (null == browser.contentWindow) {
	      accessext_consoleDump("accessext_getDocumentsFromActiveBrowser: content window not found");
	      return new Array();
	   }
	   var activeWindow = browser.contentWindow;
	   return accessext_getDocuments(activeWindow,new Array());
	},
	
	accessext_replaceAttributeWBackup: function(node,attribute,newvalue,enabled) {
	   var backupattr = "ae--" + attribute;
	   if (node.hasAttribute && node.setAttribute && node.getAttribute) {
	      if (enabled) {
	         if (node.hasAttribute(attribute)) {
	            node.setAttributeNS(accessibilityextURI,backupattr,node.getAttribute(attribute));
	         } else {
	            node.setAttributeNS(accessibilityextURI,backupattr,"ae--false--");
	         }
	         node.setAttribute(attribute, newvalue);
	      } else {
	         if (node.hasAttribute(backupattr)) {
	            if (node.getAttributeNS(accessibilityextURI,backupattr) != "ae--false--") {
	               node.setAttributeNS(null,attribute,node.getAttributeNS(accessibilityextURI,backupattr));
	            } else {
	               node.removeAttribute(attribute);
	            }
	            node.removeAttributeNS(accessibilityextURI,backupattr);
	         }
	      }
	   }
	},
	
	accessext_addStyleSheet: function(location, id,doc) {
	   var headElementList = null;
	   var linkElement     = null;
	   
	   if (null == doc || null == doc.getElementsByTagName) {
	      throw new Error('accessext_addStyleSheet: doc is null or not valid');
	   }
	
	   headElementList = doc.getElementsByTagName("head");
	   linkElement     = doc.createElement("link");
	
	   linkElement.setAttribute("href", location);
	   linkElement.setAttribute("id", id);
	   linkElement.setAttribute("rel", "stylesheet");
	   linkElement.setAttribute("type", "text/css");
	
	   // If there is a head element
	   if(headElementList.length > 0) {
	      headElementList[0].appendChild(linkElement);
	   } else {
	      doc.documentElement.childNodes[0].appendChild(linkElement);
	   }
	},
	
	accessext_removeStyleSheet: function(id,doc) {
	   var element       = null;
	
	   if (null == doc || null == doc.getElementsByTagName) {
	      throw new Error('accessext_removeStyleSheet: doc is null or not valid');
	   }
	
	   // Remove all elements with that id
	   while(doc.getElementById(id)) {
	      element = doc.getElementById(id);
	      element.parentNode.removeChild(element);
	   }
	},
	
	accessext_toggleStyleSheet: function(checked, location, id, doc) {
	   if(checked) {
	      accessext_addStyleSheet(location, id, doc);
	   } else {
	      accessext_removeStyleSheet(id,doc);
	   }
	},
	
	accessext_isChecked: function(id) {
	   var element = document.getElementById(id);
	   return element.getAttribute("checked");
	},
	
	accessext_setChecked: function(id,checked) {
	   var element = document.getElementById(id);
	   if (!element) {
	      alert(accessext_getBundleString('error.setCheckedFailed',id));
	   } else { 
	      element.setAttribute("checked",checked);
	   }
	},
	
	accessext_getBoolPref: function(prefString,def) {
	    var preferencesService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");
	   return preferencesService.getBoolPref(prefString);
	},
	
	accessext_getCharPref: function(prefString) {
	    var preferencesService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");
	   return preferencesService.getCharPref(prefString);
	},
	
	accessext_setBoolPref: function(prefString, val) {
	    var preferencesService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");
	   preferencesService.setBoolPref(prefString, val);
	},
	
	accessext_setCharPref: function(prefString, val) {
	    var preferencesService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");
	   preferencesService.setCharPref(prefString, val);
	},
	
	// Generates a page in a new tab or window depending on the user's preference in the browser
	accessext_generatePage: function(url) {
	    var preferencesService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");
	    var generatedPage = null;
	
		switch (preferencesService.getIntPref("browser.link.open_newwindow")) {
		   case 3:
			try {
				if (window.opener == null) {
			        getBrowser().selectedTab = getBrowser().addTab(url);
				} else {
		        	window.opener.getBrowser().selectedTab = window.opener.getBrowser().addTab(url);
				}
		        generatedPage = window;
			}
			catch (exception){
				//generatedPage = window.open(url);
				accessext_consoleDump('accessext_generatePage falied ', exception);
			}
		   break;
		   case 1:
			try {
				if (window.opener == null) {
		      		getBrowser().contentWindow.location = url;
		      		generatedPage = getBrowser.contentWindow;
				} else {
		        	window.opener.getBrowser().contentWindow.location = url;
		      		generatedPage = window.opener.getBrowser().contentWindow;
				}
			}
			catch (exception){
				//generatedPage = window.open(url);
				accessext_consoleDump('accessext_generatePage falied ', exception);
			}
		   break;
		//If 1, current window
		//If 2, new window
		      default:
		          generatedPage = window.open(url);        
		      break;
		   }
		if (generatedPage.content)
			return generatedPage.content.document;
		else 
			return generatedPage.content
	},
	
	/* Copied from Moz/FF source. Rather simple function, did not want to have to include a lot of other JS to use it
	   In Firefox this is located in contentAreaClick.js
	*/
	accessext_makeURLAbsolute: function(base,url) {
		try {
		   // Construct nsIURL
		   var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
		   var baseURI  = ioService.newURI(base, null, null);
		   
		   return ioService.newURI(baseURI.resolve(url), null, null).spec;
		} catch (ex) {
			accessext_consoleDump('accessext_makeURLAbsolute exception: ', ex);
		}
	},
	
	accessext_consoleDump: function(aMessage, e) {
		try {
		  var strDetails = "";
		  var consoleService = Components.classes["@mozilla.org/consoleservice;1"]
		                       .getService(Components.interfaces.nsIConsoleService);
		  if (e != null){
		  	strDetails = "Accessext: " + aMessage + " " + e.message + " (file: " + e.fileName + " lineNo:" + e.lineNumber + ")";
		  }
		  else {
		  	if (typeof(aMessage)== Error) {
		  		strDetails = "Accessext: " + aMessage.message + " (file: " + aMessage.fileName + " lineNo:" + aMessage.lineNumber + ")";
			} else {
				strDetails = "Accessext: " + aMessage;
			}
		  }	
//		  consoleService.logStringMessage(strDetails);
		} catch (ex) {
// In the focus inspector, when the objects have not fully loaded you can get this error.
//			alert('accessext_consoleDump exception: ' + ex);
		}
	},
	
	accessext_detailLog: function(aMessage) {
	   var pref = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService).getBranch("");
	   if (pref.prefHasUserValue("accessext.detailLogging") && pref.getBoolPref("accessext.detailLogging")) {
	      accessext_consoleDump("DETAIL: " + aMessage);
	   }
	},
	
	loadProperties: function() {
	var src = 'chrome://accessext/locale/en-US/accessext.properties';
	  var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"].getService(Components.interfaces.nsIStringBundleService);
	return strBundleService.createBundle(src); 
	//var bundle = stringBundleService.CreateBundle(src, appLocale);
	//return bundle;
	},
	
	accessext_getBundleSingleString: function(id) {
	   var aeStrings = document.getElementById('accessext_bundle');
	   var keyStrings = document.getElementById('keybindings_bundle');
	   var inspectorStrings = document.getElementById('inspector_bundle');
	   var retStr;
	   
	   // Of course, smart way to do this if we make any more bundles is to put them in an array and loop
	   try {
	      if (aeStrings) {
	         if( (retStr = aeStrings.getString(id)) != null ) {
	            return retStr;
	         }
	      }
	   } catch (e) {} // exception just means we couldn't find the string
	   
	    try {
	      if (keyStrings) {
	         if( (retStr = keyStrings.getString(id)) != null ) {
	            return retStr;
	         }
	      }
	   } catch (e) {} // exception just means we couldn't find the string
	   
	   try {
	      if (inspectorStrings) {
	         if( (retStr = inspectorStrings.getString(id)) != null ) {
	            return retStr;
	         }
	      }
	   } catch(e) {
	   		accessext_consoleDump('Failed to load string with id ' + id, e);
	   }
	   return false;
	},
	
	accessext_getBundleString: function(id,vars) {
	   try {
	      var aeStrings = document.getElementById('accessext_bundle');
	      var keyStrings = document.getElementById('keybindings_bundle');      
	      var inspectorStrings = document.getElementById('inspectorbindings_bundle');      
	      var retStr;
	
	      if (typeof(vars) == 'string') {
	         vars = new Array(vars);
	      }
	      if (aeStrings) {
	       if( (retStr = aeStrings.getFormattedString(id,vars)) != null ) {
	            return retStr;
	         }
	      }
	      if (keyStrings) {
	         if( (retStr = keyStrings.getFormattedString(id,vars)) != null ) {
	            return retStr;
	         }
	      }
	      if (inspectorStrings) {
	         if( (retStr = inspectorStrings.getFormattedString(id,vars)) != null ) {
	            return retStr;
	         }
	      }
	
	      throw "String Not Found";     
	   } catch (ex) {
	      accessext_consoleDump('Failed to load string with id ' + id, ex);
	   }
	   
	   return false;
	},
	
	accessext_keyboardFocusNode: function(node,awindow) {
	   var prefService = Components.classes["@mozilla.org/preferences-service;1"]
	      .getService(Components.interfaces.nsIPrefService);
	   prefService = prefService.getBranch(null);
	
	   assert(node != null);
	   
	   var selection;
	   try {
	      /* @TODO : All caret code should be handled here. I cant seem to get it to work correctly */
	      selection = awindow.getSelection();
	      selection.removeAllRanges();
	      selection.collapse(node, 0);
	      selection.removeAllRanges();
	      var range = node.ownerDocument.createRange();
	      range.selectNode(node);
	      selection.addRange(range);
	      //Re-enable the caret
	      //prefService.setBoolPref("accessibility.browsewithcaret",true);
	   } catch(e) {
	      accessext_consoleDump('Error in focusing caret on a node: ' + e.toString(), e);
	      return false;
	   }
	   return true;
	},
	
	accessext_getNodeDocPosition: function(node) {
	   if (node != null) {
	      var r = new Object();
	      r.x = node.offsetLeft;
	      r.y = node.offsetTop;
	      r.width = node.offsetWidth;
	      r.height = node.offsetHeight;
	      var thisNode = node;
	      while (thisNode.offsetParent &&
	         (thisNode.offsetParent != node.ownerDocument)) {
	         thisNode = thisNode.offsetParent;
	         r.x += thisNode.offsetLeft;
	         r.y += thisNode.offsetTop;
	      }
	      
	      return r;
	   }
	   return null;
	},
	
	accessext_scrollToNode: function(node) {
	   var offset = accessext_getNodeDocPosition(node);
	   if (offset != null) {
	      AE.thewindow.scrollTo(offset.x, offset.y);
	   }
	},
	
	// Do N O T pass in null for optionalOffsetNode, leave it blank if you are not using it.
	accessext_scrollToNodeCentered: function(node,optionalOffsetNode) {
	   if (node == null) {
	      return;
	   }
		var offsetNode = optionalOffsetNode;
		if (optionalOffsetNode === undefined) {
	    	offsetNode = node.offsetParent;
		} 
	   
	   if (offsetNode == null) {
	      return;
	   }
	 
	   var offset = accessext_getNodeDocPosition(node);
	   var parentOffset = accessext_getNodeDocPosition(offsetNode);
	   
	   if (offset != null && parentOffset != null) {
	      
	      //var x = parentOffset.x;
	      //var y = parentOffset.y;
	      var x = node.ownerDocument.defaultView.scrollX;
	      var y = node.ownerDocument.defaultView.scrollY;
	      var botX = x + node.ownerDocument.defaultView.innerWidth;
	      var botY = y + node.ownerDocument.defaultView.innerHeight;
	
	      var objX = offset.x + offset.width;
	      var objY = offset.y + offset.height;
	/*
	      x = offset.x - (node.ownerDocument.defaultView.innerWidth /2);
	      y = offset.y - (node.ownerDocument.defaultView.innerHeight /2);*/
	 
	//SMF if the node is a subnode of an iframe the window is not scroll so the iframe is with in view
	// the following stops at the document just before the iframe.
	/*var inIFrame = false;
		var testNode = node.parentNode;
		while (!inIFrame && testNode) {
			if (testNode.nodeName.toUpperCase() == 'IFRAME'){inIFrame = true;}
			else {testNode = testNode.parentNode}
		}
		if (testNode){
		      if (offset.y > botY) {
		         y = objY - testNode.ownerDocument.defaultView.innerHeight;
		      } else if (offset.y < testNode.ownerDocument.defaultView.scrollY) {
		         y = offset.y;
		      }
		      
		      if (offset.x > botX) {
		         x = objX - testNode.ownerDocument.defaultView.innerWidth;
		      } else if (offset.x < testNode.ownerDocument.defaultView.scrollX) {
		         x = offset.x;
			  }	
		}
	*/
	
	
	      //NEW NEW behavior - act like the tab key
	      if (offset.y > botY) {
	         y = objY - node.ownerDocument.defaultView.innerHeight;
	      } else if (offset.y < node.ownerDocument.defaultView.scrollY) {
	         y = offset.y;
	      }
	      
	      if (offset.x > botX) {
	         x = objX - node.ownerDocument.defaultView.innerWidth;
	      } else if (offset.x < node.ownerDocument.defaultView.scrollX) {
	         x = offset.x;
		  }
	      
	      //Make sure we don't go out of bounds
	      x = Math.max(x,0);
	      y = Math.max(y,0);
	      x = Math.min(node.ownerDocument.defaultView.scrollMaxX,x);
	      y = Math.min(node.ownerDocument.defaultView.scrollMaxY,y);
	
	      node.ownerDocument.defaultView.scrollTo(x, y);
	   }
	},
	
	   /* Open flags 
	       #define PR_RDONLY       0x01
	       #define PR_WRONLY       0x02
	       #define PR_RDWR         0x04
	       #define PR_CREATE_FILE  0x08
	       #define PR_APPEND      0x10
	       #define PR_TRUNCATE     0x20
	       #define PR_SYNC         0x40
	       #define PR_EXCL         0x80
	    */
	    /*
	    ** File modes ....
	    **
	    ** CAVEAT: 'mode' is currently only applicable on UNIX platforms.
	    ** The 'mode' argument may be ignored by PR_Open on other platforms.
	    **
	    **   00400   Read by owner.
	    **   00200   Write by owner.
	    **   00100   Execute (search if a directory) by owner.
	    **   00040   Read by group.
	    **   00020   Write by group.
	    **   00010   Execute by group.
	    **   00004   Read by others.
	    **   00002   Write by others
	    **   00001   Execute by others.
	    **
	    */
	
	writeFile : function( str, filename ) {
	   var profiledir = AE.DirIO.get('UChrm');
	   profiledir.append('accessexttemp');
	   if (profiledir.exists()) {
	   } else {
		   AE.DirIO.create(profiledir);
	      if (profiledir.exists()) {
	      } else {
	         alert('Failed to create temporary directory' + AE.DirIO.path(profiledir));
	         return false;
	      }
	   }
	   profiledir.append(filename);
	   
	   var fileOut = AE.FileIO.open(profiledir.path);/*DirIO.path(profiledir).substr(8,1000)*/
	   var rv = AE.FileIO.write(fileOut,str);
	   return AE.DirIO.path(profiledir);
	},
	
	getFieldSetNodeForElement: function(element) {
	   var temp;
	   temp = element;
	   while (temp != null && temp.tagName && temp.tagName.toLowerCase() != 'fieldset') {
	      temp = temp.parentNode;
	   }
	   if (temp != null && temp.tagName && temp.tagName.toLowerCase() == 'fieldset') {return temp;}
	   else { return null; }
	},
	
	accessext_canTagHaveAlt: function(tagname) {
	   switch (tagname.toLowerCase()) {
	      case 'img':
	      case 'area':
	      case 'applet':
	      case 'input'://should really only be be for type=image
	         return true;
	   }
	   
	   return false;
	},
	
	accessext_nodeHanlder_displayableText: function(node) {
	   //accessext_consoleDump("Node is of type " + node.nodeType + " name : " + node.nodeName);
	/*
	ACCEPT: Use this
	SKIP : Dont use, but traverse children
	REJECT : Dont use, do not traverse children
	*/
	   if (node.nodeType == node.TEXT_NODE) {
	      return NodeFilter.FILTER_ACCEPT;
	   }
	
	   if (node.nodeType == node.ELEMENT_NODE) {
	      if (node instanceof Components.interfaces.nsIDOMHTMLElement) {
	         if (node.hasAttribute) {
	            return ((node.hasAttribute('alt') && (accessext_canTagHaveAlt(node.tagName))) || node.hasAttribute('title')) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
	         }
	      }
	   }
	   //accessext_consoleDump('Did not encounter anything, retunring.' + node.textContent);
	   return NodeFilter.FILTER_SKIP;
	},
	
	getDisplayableTextRecursively: function(node) {
	   var walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,accessext_nodeHanlder_displayableText, true);
	   
	   var cur = null;
	   var retStr = "";
	   for (cur = walker.firstChild(); cur != null; cur = walker.nextSibling()) {
	      if (cur.nodeType == node.TEXT_NODE) {
	         if (retStr != "") {
	            retStr = retStr + " ";
	         }
	         retStr = retStr + cur.textContent;
	      } else if (cur.hasAttribute) {
	         if (cur.hasAttribute('alt') && (accessext_canTagHaveAlt(cur.tagName))) {
	            if (retStr != "") {
	               retStr = retStr + " ";
	            }
	            retStr = retStr + cur.getAttribute('alt');
	         } else if (cur.hasAttribute('title')) {
	            if (retStr != "") {
	               retStr = retStr + " ";
	            }
	            retStr = retStr + cur.getAttribute('title');
	         }
	      }
	   }
	   
	   return cleanSpaces(retStr);
	},
	getDisplayableAltRecursively: function(node) {
	   var walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,accessext_nodeHanlder_displayableText, true);
	   
	   var cur = null;
	   var retStr = "";
	   for (cur = walker.firstChild(); cur != null; cur = walker.nextSibling()) {
	      if (cur.nodeType != node.TEXT_NODE && cur.hasAttribute) {
	         if (cur.hasAttribute('alt') && (accessext_canTagHaveAlt(cur.tagName))) {
	            if (retStr != "") {
	               retStr = retStr + " ";
	            }
	            retStr = retStr + cur.getAttribute('alt');
	         } 
	      }
	   }
	   
	   return cleanSpaces(retStr);
	},
	
	getAllXpathResultNodes: function(node,xpath) {
		try {
			if (xpath == '' || node == null) return null;
			var xpathResult = node.ownerDocument.evaluate(xpath, node, accessext_NSResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
			var resultNodes = new Array();
			for (var i = 0; i < xpathResult.snapshotLength; i++) {
					resultNodes[resultNodes.length] = xpathResult.snapshotItem(i);
			}
			return resultNodes;
		} catch (ex) {
			accessext_consoleDump('getAllXpathResultNodes exception: ' + ex.message + ' ' + xpath);
		}
	},
	
	getNodesWithAttribute: function(doc,attribute) {
	   return getAllXpathResultNodes(doc,"//*[@"+attribute+"]");
	},
	
	findTextBeforeNode: function(myObj, numPreviousNodesToCheck) {
	   var numNodesChecked = 0;
	   var node = myObj.node;	
	   var walker = node.ownerDocument.createTreeWalker(node.ownerDocument.documentElement,NodeFilter.SHOW_TEXT,null,true);
	   walker.currentNode = node;
	   var cur = walker.previousNode();
	   while (null != cur && numNodesChecked < numPreviousNodesToCheck) {
	   	  if (cur.nodeType != node.TEXT_NODE) {return '';} // this line may not be necessary
	      if (cleanSpaces(cur.textContent) != '') {
		  	return cur.textContent; 
		  } 
		  
	      cur = walker.previousNode();
		  numNodesChecked++;
	   }
	   return '';
	},
	
	findTitleHeaderBeforeNode: function(myObj) {
		try {
		   function headerCheck(cur, myObj) {
			   if (cur != null) {
				   var tagName = cur.tagName.toLowerCase();
				   if (tagName == 'h2' || tagName == 'h3' || tagName == 'h4' || tagName == 'h5' || tagName == 'h6') {
					   myObj.title = cleanSpaces(cur.textContent);
					   myObj.id = GetValueFromAttributes(cur,['id'],false);
					   myObj.relatednode = cur;		
					   return cleanSpaces(cur.textContent);	 
				   }
			   }
			   return null;
		   }
			   
		   var numSpanDiv = 0;
		   var node = myObj.node;	
		   var walker = node.ownerDocument.createTreeWalker(node.ownerDocument.documentElement,NodeFilter.SHOW_ELEMENT,null,true);
		   walker.currentNode = node;
		   var cur = walker.previousNode();
		   while (null != cur && cleanSpaces(cur.textContent) == '') cur = walker.previousNode();
		   var val = headerCheck(cur, myObj);
		   if (val != null) return val; //found a header immediately before the list

		   walker.currentNode = node;
		   var prev = walker.parentNode();
		   var cur = walker.parentNode();
		   var tagName = cur.tagName.toLowerCase();
		   while (null != cur && (tagName == 'ul' || tagName == 'ol' || tagName == 'li')) {
			   prev = cur;
			   cur = walker.parentNode();	
			   if (null != cur) tagName = cur.tagName.toLowerCase();
		   }
		   walker.currentNode = prev;
		   cur = walker.previousNode();
		   val = headerCheck(cur, myObj);
		   if (val != null) return val; // there is a header before the top level UL see http://accountability.illinois.gov/ for testing 
		   
		   return false;
		} catch (e) {
			accessext_consoleDump(e.message);
		}
	},
	
	findARIATitleHeaderBeforeNode: function(myObj, numPreviousNodesToCheck) {
	   var numNodesChecked = 0;
	   var node = myObj.node;	
	   var walker = node.ownerDocument.createTreeWalker(node.ownerDocument.documentElement,NodeFilter.SHOW_ELEMENT,null,true);
	   walker.currentNode = node;
	   var cur = walker.previousNode();
	   while (null != cur && numNodesChecked < numPreviousNodesToCheck) {
		  var role = GetValueFromAttributes(cur,['role'],"");
	// KILL NAMESPACE CODE	  
	//	  if (role.toLowerCase().indexOf('wairole:heading') != -1) { // assume ARIA headings are acceptable
		  if (role.toLowerCase() == 'heading') { // assume ARIA headings are acceptable
			 	myObj.role = 'heading'; //'wairole:heading'
				myObj.title = cleanSpaces(cur.textContent);
				myObj.id = GetValueFromAttributes(cur,['id'],false);
				myObj.relatednode = cur;
	            return cleanSpaces(cur.textContent);
		  } 
	      cur = walker.previousNode();
		  numNodesChecked++;
	   }
	   return false;
	},
	// Opens a toolbar button automatically if another toolbar button is open on the toolbar
	accessext_openToolbarButton: function(currentToolbarButton)
	{
//		alert('in accessext_openToolbarButton'); return;
		
	    // If the toolbar button is set and is not open
	    if(currentToolbarButton && !currentToolbarButton.open)
	    {
	        var toolbarButton        = null;
	        var toolbarButtons       = currentToolbarButton.parentNode.getElementsByTagName("toolbarbutton");
	        var toolbarButtonsLength = toolbarButtons.length;
	
	        // Loop through the toolbar buttons
	        for(var i = 0; i < toolbarButtonsLength; i++)
	        {
	            toolbarButton = toolbarButtons.item(i);
	
	            // If the toolbar button is set, is not the same toolbar button and is open
	            if(toolbarButton && toolbarButton != currentToolbarButton && toolbarButton.open)
	            {
	                toolbarButton.open        = false;
	                currentToolbarButton.open = true;
	
	                break;
	            }
	        }
	    }
	},
	
	getAltTextFromNode: function(childNode) {
		if(childNode.getAttribute("alt")) {
		//	return " {" + childNode.nodeName + "-ALT: " + childNode.getAttribute("alt") + "} ";
			return  childNode.getAttribute("alt");
		} else {
			return "";
		}
	},
	
	/**
	 * @param {Node} node a Node object from the DOM tree structure
	 * @return the text from all text nodes and image element alt attributes found within the given node
	 */
	getNodeText: function(node) {
		var text = "";
		for(var i = 0; i < node.childNodes.length; i++) {
			var childNode = node.childNodes[i];
			
			if(childNode.nodeName == "#text") {
				text += childNode.nodeValue + "";
			} else if(childNode.nodeName == "IMG" || 
					//childNode.nodeName == "APPLET" ||
					//childNode.nodeName == "EMBED" ||
					childNode.nodeName == "AREA") {
				text += getAltTextFromNode(childNode);
			} else if(childNode.nodeName == "OBJECT"){
				text += " {OBJECT-ALT: " + getNodeText(childNode) + "} ";
			} else if(childNode.nodeName == "BR") {
				text += " ";
			} else if(childNode.hasChildNodes()) {
				text += getNodeText(childNode) + "";
			} else if(childNode.nodeName.toLowerCase() == "input") { // 11/7/07 SMF otherwise no text is found for checkbox w/o text
				GetValueFromAttributes(childNode,['title'],"")
				text += cleanSpaces(GetValueFromAttributes(childNode,['title'],""));
			} else {
				//do nothing
			}
			
		}
		return text;
	},
	
	/**
	 * Retrieve nodes by their ids.
	 * @param {doc}  the document we want search in for the id
	 * @param {targetids}  the id of the node we are looking for
	 * @return arraynode an array of nodes that match the targetids
	 * @author Sandy Foltz
	*/
	accessext_getnodebyid: function(doc, targetids) {
		var id ="";
	
		var xp = "//*[@id]";
		var xpathResult = doc.evaluate(xp, doc, accessext_NSResolver, XPathResult.ANY_TYPE,null);
		var r = xpathResult.iterateNext();
		while (r) {
			id = GetValueFromAttributes(r,['id'],"");
			if (id == targetids) {
				return r;
			}
			r = xpathResult.iterateNext();
		}
		return null;
	},
	
	accessext_getfirstvisiblenode: function(node) {
		var r = new Object();
		r = accessext_getNodeDocPosition(node);
		if (!(((r.x < 0) || (r.y < 0)) || (node.nodeName.toUpperCase() == 'HIDDEN') || ((r.width <= 0) && (r.height <= 0))) ) {
			return node; //already visible
		}
	
	   var walker = document.createTreeWalker(node, NodeFilter.SHOW_ELEMENT ,accessext_nodeHanlder_displayableText, true);
	   var offscreen = true;
	   var cur = null;
	   for (cur = walker.firstChild(); cur != null && offscreen == true; cur = walker.nextSibling()) {
		  r = accessext_getNodeDocPosition(cur);
		  if (((r.x < 0) || (r.y < 0)) || (cur.nodeName.toUpperCase() == 'HIDDEN') || ((r.width <= 0) && (r.height <= 0)) ) {
		  	offscreen = true;
		  } else {
		  	offscreen = false;
		  }
	   }
	   
	   if (offscreen == false){
	   	return cur;
	   } else {
	   	return null;
	   }
	},
	
	/**
	 * getTextAssociatedWithIDs retrieves node title/text by their ids.
	 * This would typically be used to read nodes referenced in a labelledby or describedby attribute.
	 * @param {doc}  the document we want search in for the ids
	 * @param {targetids}  the ids of the node we are looking for
	 * @return returnarray [0]=node title/text by their ids; [1]=unfound ids, [2]=first node with visible text (for highlighting)
	 * @author Sandy Foltz
	*/
	getTextAssociatedWithIDs: function(doc, targetids, usefirstidonly) {
		var id ="";
		var nodeArray = [];
		var targetNotFoundArray = [];
		var targetArray = targetids.split(" ");
		var found = false;
	
		if (usefirstidonly){
			var temp = targetArray[0];
			targetArray = [];
			targetArray.push(temp);
		}
		for (i=0; i < targetArray.length; i++) {nodeArray.push(null); targetNotFoundArray.push(targetArray[i]);}
	
		var xp = "//*[@id]";
		var xpathResult = doc.evaluate(xp, doc, accessext_NSResolver, XPathResult.ANY_TYPE,null);
		var r = xpathResult.iterateNext();
		while (r) {
			id = GetValueFromAttributes(r,['id'],"");
			for (i=0; i < targetArray.length; i++) {
				if (id == targetArray[i]){
					nodeArray[i] = r;
					targetNotFoundArray[i] = null;
					found = true;
				} 
			}
			r = xpathResult.iterateNext();
		}
		
		var masterArray = [];
		var returntext = '';
		var returnnotfoundids = '';
		var firstvisibleobjfound = null;
		for (var j=0; j < targetNotFoundArray.length; j++) {
	 		if (nodeArray[j] != null) {
				if (firstvisibleobjfound == null){firstvisibleobjfound = accessext_getfirstvisiblenode(nodeArray[j]);}
				singleText = cleanSpaces(GetValueFromAttributes(nodeArray[j],['title'],""));
				if (singleText == "") { singleText = cleanSpaces(getNodeText(nodeArray[j])); } 
				returntext += ' ' + singleText; 
			}
			if (targetNotFoundArray[j] != null) {returnnotfoundids += targetNotFoundArray[j] + ', ';}
		}
		
		if (cleanSpaces(returnnotfoundids).length >= 2) returnnotfoundids = returnnotfoundids.substring(0, returnnotfoundids.length-2); else returnnotfoundids = null;
		masterArray.push(returntext);
		masterArray.push(returnnotfoundids);
		masterArray.push(firstvisibleobjfound);
	 	return masterArray;
	},
	
	
	currStandard: function() {
		var pref = getAccessextPrefService();
		if (pref.prefHasUserValue("options.prefStandard")) {
			var standard = pref.getCharPref("options.prefStandard");
			return standard;
		}
		return 'icita07';
	},
	
	getAccesessibilityInfoCaption: function() {
		try {
			var standard = currStandard();
			standard = accessext_getBundleSingleString('column.header.' + standard + '.label');
		    return standard + ' - ' + accessext_getBundleSingleString('inspector.groupbox.note.caption');
		} catch (ex) {
			accessext_consoleDump('getAccesessibilityInfoCaption ex: ' + ex);
		}
	},
	
	
	accesxt_generateHTMLTable: function(tableName, srcarray, tableheader) {
		var html = '';
		var note = '';
		var std = currStandard();
		var skipStd = 'icita08';
		var skipAria = false;
		if (std =='icita08'){skipStd = 'icita07';}
			try {	
				if (srcarray.length > 0) {
					var obj = srcarray[0];
					var colDefs = obj.displayDetails;
					
					if (tableheader != undefined) {
						html = html + '<h3>' + tableheader + '</h3>';
					}
					var attributes = 'title="' + tableName + '" summary="' + accessext_getBundleString('dataTables.summary', tableName) + '"';
					html = html + '<table border=1 "' + attributes + '><tr>';
					for (var j = 0; j < colDefs.dataElementNames.length; j++) { //for each column to be displayed
						if (colDefs.columnHeaders[j].indexOf(skipStd) == -1) {
							html = html + '<th scope="col">' + accessext_getBundleSingleString(colDefs.columnHeaders[j]) + '</th>';
						}
					}
					if (colDefs.accessIssuesDataElementNames.length  > 0) {
						html = html + '<th scope="col">' + getAccesessibilityInfoCaption() + '</th>';
					}
					if ( typeof obj.listDetailsInHTMLFormat == 'function' ) {
						html = html + "<th>Details</th>";
					}
					html = html + "</tr>";
					for (var i = 0; i < srcarray.length; i ++) { //for each treerow
						if (srcarray[i].display) {
							html = html + "<tr>";
							obj = srcarray[i];
							for (var j = 0; j < colDefs.dataElementNames.length; j++) { //for each column to be displayed
								if (colDefs.columnHeaders[j].indexOf(skipStd) == -1) {
									colName = colDefs.dataElementNames[j];
									var value = (obj[colName] == null) ? "" : obj[colName].toString();
									html = html + "<td valign=top>" + value + "&nbsp;</td>"; //<PRE> & <CODE> don't have the desired effect
								}
							}
		 					if (i < srcarray.length && colDefs.accessIssuesDataElementNames.length > 0) {
								if ( typeof( srcarray[i].displayDetails.accessIssuesUseObjectFunctions ) != "undefined" ) {
									if (srcarray[i].displayDetails.accessIssuesUseObjectFunctions == true) {
						        		note = srcarray[i].getNoteText();
									}
								} else {
						        	note = getNoteText(srcarray[i]);
								}
								
								note = note.replace(/ \n/g, '<br>');
								html = html + "<td valign=top>" + note + " &nbsp;</td>";
							}
							if ( typeof srcarray[i].listDetailsInHTMLFormat == 'function' ) {
								html = html + "<td valign=top>" + srcarray[i].listDetailsInHTMLFormat() + " &nbsp;</td>";
							}
							html = html + "</tr>";
						}
					}
					html = html + "</table>";
				}
			}
			catch(e){ // treebox_main may not exist on all pages
				accessext_consoleDump('accesxt_generateHTMLTable exception: ', e);
			}
		return html;
	},
	
	accessExtAsBrowserPage: function(srcarray){
		var outputtable="";
		var documents = accessext_getDocuments(AE.thewindow,new Array());
		var doc = documents[0];
	    var dialogTitle = document.title;
	    
		var outputString = "<html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en-us' lang='en-us' xmlns:x2='http://www.w3.org/TR/xhtml2'>";
		outputString += "<head><title>Accessibility Extension - " + dialogTitle + " Summary</title></head>";
		outputString += "<body><h1>Accessibility Extension</h1><h2>" + dialogTitle + " Summary</h2>";
		var currentTime = new Date()
		var month = currentTime.getMonth() + 1
		var day = currentTime.getDate()
		var year = currentTime.getFullYear()
		var hours = currentTime.getHours()
		var minutes = currentTime.getMinutes()
		if (minutes < 10) {	minutes = "0" + minutes }
		var strAMPM = "AM";
		if(hours > 11){strAMPM ="PM"}
	
	
		if ( typeof( srcarray ) != "undefined" ) {
	   		outputtable = accesxt_generateHTMLTable(dialogTitle, srcarray);
		}
		else if ( typeof( AE.primaryarray ) != "undefined" ) {
	   		outputtable = accesxt_generateHTMLTable(dialogTitle, AE.primaryarray);
		}
		else {
	    	outputtable = accesxt_generateHTMLTable(dialogTitle, AE.dataarray);
		}
		if (outputtable == "") {
			alert('There is no data to be viewed.')
			return;
		}
		outputString += outputtable + "<br>Date: " + month + "/" + day + "/" + year + " " + hours + ":" + minutes + " " + strAMPM + "<br>";
		outputString += "URL: " + doc.documentURI + "<br>";
		outputString += "</body>";
		
		try {
			var accessibleHTMLPage = window.open("Template.html", "AccessExt as broswer page", 'status=1,toolbar=1,scrollbars=1,menubar=1') 
		} catch (ex) {
			accessext_consoleDump('accessExtAsBrowserPage exception: ' + ex);
		}
	    
		function tryAgain() {
			var bodyElement = accessibleHTMLPage.document.getElementById("bodyID");
	        if (bodyElement) {
	 			bodyElement.innerHTML = outputString;
	        }
	        else {
	             setTimeout (tryAgain, 500);
	        }
		}
	    try {
	 		var bodyElement = accessibleHTMLPage.document.getElementById("bodyID");
			if (!(bodyElement))
	            throw "tantrum";
			else {
	 			bodyElement.innerHTML = outputString;
			}       
	    }
	    catch (ex) {
	        setTimeout (tryAgain, 500);
	        return;
	    }// need to wait until this loads	
	},
	
	loadSidebar : function(tool)
	{
		try {
			openWebPanel("Extensions", "chrome://accessext/content/" + tool + ".xul"); 
		} catch (ex) {
			accessext_consoleDump('loadSidebar exception: ', ex);
		}
	},
	
	getBaseURI: function(node) {
		var baseURI = '';
		
		var p = node.parentNode; //= node; // 
		while (p != null && baseURI == '') {
			if (node.baseURI) {
				baseURI = node.baseURI;
			}
			p = p.parentNode;
		}
		return baseURI;
	}
});
}});
